﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using Algobox.JuicyLib.MarketData.Structure;
using System.IO;

namespace Algobox.JuicyLib.Apps.Yahoo
{
    public static class YahooClient
    {
        const string UrlQueryRedirect = "http://uk.finance.yahoo.com/q?s=";
        const string UrlQueryMain = "http://finance.yahoo.com/q?s=";
        
        const string UrlQuote = "http://uk.old.finance.yahoo.com/d/quotes.csv?f=st1ohgpv&e=.csv&s=";
        const string UrlLast = "http://uk.old.finance.yahoo.com/d/quotes.csv?f=sl1&e=.csv&s=";
        const string UrlHistory = "http://ichart.yahoo.com/table.csv?s=";
        const string UrlHistorySpecific = "http://ichart.finance.yahoo.com/table.csv?s={0}&a={1}&b={2}&c={3}&d={1}&e={2}&f={3}&g=d&ignore=.csv";

        const int HISTORY_DATE = 0;
        const int HISTORY_OPEN = 1;
        const int HISTORY_HIGH = 2;
        const int HISTORY_LOW = 3;
        const int HISTORY_CLOSE = 6;
        const int HISTORY_VOLUME = 5;

        const int QUOTE_SYMBOL = 0;
        const int QUOTE_TIME = 1;
        const int QUOTE_DATE = 2;
        const int QUOTE_OPEN = 3;
        const int QUOTE_HIGH = 4;
        const int QUOTE_LOW = 5;        
        const int QUOTE_CLOSE = 7;
        const int QUOTE_VOLUME = 7;

        const int LAST_SYMBOL = 0;
        const int LAST_PRICE = 1;

        /// <summary>
        /// Gets all of the previous history records
        /// </summary>
        /// <param name="symbol"></param>
        /// <param name="data"></param>
        /// <returns></returns>
        public static bool TryGetHistory(string symbol, out Dictionary<DateTime, OHLC> data)
        {
            data = new Dictionary<DateTime, OHLC>();

            string[] rows;

            if (!TryExtractHistory(symbol, out rows))
            {
                // failed to get data so perhaps symbol is wrong, let try and get the correct symbol
                bool found = TryExtractSymbolQuote(symbol, out symbol);
                if (!found || (found && !TryExtractHistory(symbol, out rows)))
                {
                    // failed to get data so perhaps symbol is wrong, let try and get the correct symbol
                    if (!TryExtractSymbolRedirect(symbol, out symbol))
                        return false;

                    // ok we got a new symbol let try get the data again
                    if (!TryExtractHistory(symbol, out rows))
                        return false;
                }
            }


            // ok we got the data let's parse it
            OHLC item;
            DateTime date;

            string[] elements;
            decimal price;
            long volume;

            foreach (string line in rows)
            {
                item = new OHLC();

                elements = line.Split(',');

                if (!DateTime.TryParse(elements[HISTORY_DATE], out date))
                    continue;
                
                if(!Decimal.TryParse(elements[HISTORY_OPEN], out price))
                    continue;
                item.Open = price;

                if (!Decimal.TryParse(elements[HISTORY_HIGH], out price))
                    continue;
                item.High = price;

                if (!Decimal.TryParse(elements[HISTORY_LOW], out price))
                    continue;
                item.Low = price;

                if (!Decimal.TryParse(elements[HISTORY_CLOSE], out price))
                    continue;
                item.Close = price;

                if (!Int64.TryParse(elements[HISTORY_VOLUME], out volume))
                    continue;
                item.Volume = volume;

                // save the item
                data.Add(date, item);
            }


            return true;
        }



        /// <summary>
        /// Gets the most recent history record only
        /// </summary>
        /// <param name="symbol"></param>
        /// <param name="date"></param>
        /// <param name="item"></param>
        /// <returns></returns>
        public static bool TryGetHistory(string symbol, out DateTime date, out OHLC item)
        {
            string[] elements;
            date = DateTime.MinValue;
            item = new OHLC();

            // check we got the data straight off the bat
            if (!TryExtractHistorySingle(symbol, out elements))
            {
                // failed to get data so perhaps symbol is wrong, let try and get the correct symbol
                bool found = TryExtractSymbolQuote(symbol, out symbol);
                if (!found || (found && !TryExtractHistorySingle(symbol, out elements)))
                {
                    // failed to get data so perhaps symbol is wrong, let try and get the correct symbol
                    if (!TryExtractSymbolRedirect(symbol, out symbol))
                        return false;

                    // ok we got a new symbol let try get the data again
                    if (!TryExtractHistorySingle(symbol, out elements))
                        return false;
                }
            }

            // ok we got the data let's parse it

            decimal price;
            long volume;

            if (!DateTime.TryParse(elements[HISTORY_DATE], out date))
                return false;

            if (!Decimal.TryParse(elements[HISTORY_OPEN], out price))
                return false;
            item.Open = price;

            if (!Decimal.TryParse(elements[HISTORY_HIGH], out price))
                return false;
            item.High = price;

            if (!Decimal.TryParse(elements[HISTORY_LOW], out price))
                return false;
            item.Low = price;

            if (!Decimal.TryParse(elements[HISTORY_CLOSE], out price))
                return false;
            item.Close = price;

            if (!Int64.TryParse(elements[HISTORY_VOLUME], out volume))
                return false;
            item.Volume = volume;


            return true;
        }


        /// <summary>
        /// Gets the history for the specific date
        /// </summary>
        /// <param name="symbol"></param>
        /// <param name="date"></param>
        /// <param name="item"></param>
        /// <returns></returns>
        public static bool TryGetHistory(string symbol, DateTime date, out OHLC item)
        {
            string[] elements;
            item = new OHLC();

            // check we got the data straight off the bat
            if (!TryExtractHistorySpecific(symbol, date, out elements))
            {
                // failed to get data so perhaps symbol is wrong, let try and get the correct symbol
                bool found = TryExtractSymbolQuote(symbol, out symbol);
                if (!found || (found && !TryExtractHistorySingle(symbol, out elements)))
                {
                    // failed to get data so perhaps symbol is wrong, let try and get the correct symbol
                    if (!TryExtractSymbolRedirect(symbol, out symbol))
                        return false;

                    // ok we got a new symbol let try get the data again
                    if (!TryExtractHistorySpecific(symbol, date, out elements))
                        return false;
                }
            }

            // ok we got the data let's parse it

            decimal price;
            long volume;

            if (!DateTime.TryParse(elements[HISTORY_DATE], out date))
                return false;

            if (!Decimal.TryParse(elements[HISTORY_OPEN], out price))
                return false;
            item.Open = price;

            if (!Decimal.TryParse(elements[HISTORY_HIGH], out price))
                return false;
            item.High = price;

            if (!Decimal.TryParse(elements[HISTORY_LOW], out price))
                return false;
            item.Low = price;

            if (!Decimal.TryParse(elements[HISTORY_CLOSE], out price))
                return false;
            item.Close = price;

            if (!Int64.TryParse(elements[HISTORY_VOLUME], out volume))
                return false;
            item.Volume = volume;


            return true;
        }



        /// <summary>
        /// Gets the history for the specific date asynchronously
        /// </summary>
        /// <param name="symbol"></param>
        /// <param name="date"></param>
        /// <param name="item"></param>
        /// <returns></returns>
        public static bool TryGetHistorySpecificAsync(string symbol, DateTime date, object handle, Action<bool, OHLC, object> result)
        {
            string[] elements;

            try
            {
                // Yahoo! subtracks one month for some reason???
                string query = String.Format(UrlHistorySpecific, symbol, date.Month - 1, date.Day, date.Year);

                WebClient webClient = new WebClient();

                webClient.DownloadDataCompleted += (sender, e) =>
                    {
                        OHLC item = new OHLC();
                        if (e.Error != null) result(false, item, handle);
                        else
                        {
                            var data = new System.Text.UTF8Encoding().GetString(e.Result).Split('\n');
                            elements = data[1].Split(',');

                            decimal price;
                            long volume;

                            if (!DateTime.TryParse(elements[HISTORY_DATE], out date))
                            { result(false, item, handle); return; }

                            if (!Decimal.TryParse(elements[HISTORY_OPEN], out price))
                            { result(false, item, handle); return; }
                            item.Open = price;

                            if (!Decimal.TryParse(elements[HISTORY_HIGH], out price))
                            { result(false, item, handle); return; }
                            item.High = price;

                            if (!Decimal.TryParse(elements[HISTORY_LOW], out price))
                            { result(false, item, handle); return; }
                            item.Low = price;

                            if (!Decimal.TryParse(elements[HISTORY_CLOSE], out price))
                            { result(false, item, handle); return; }
                            item.Close = price;

                            if (!Int64.TryParse(elements[HISTORY_VOLUME], out volume))
                            { result(false, item, handle); return; }
                            item.Volume = volume;

                            if(item.Close == 0)
                                { result(false, item, handle); return; }

                            // we have the values!
                            result(true, item, handle);
                        }

                    };

                webClient.DownloadDataAsync(new Uri(query));

                return true;
            }
            catch (Exception e)
            {
                Console.WriteLine("Failed TryGetHistorySpecificAsync for {0} {1}", symbol, date);
                Console.WriteLine(e);
            }
            return false;
        }


        public static bool TryGetPreviousClose(string symbol, object handle, Action<bool, decimal, object> result)
        {
            try
            {
                string query = UrlQueryMain + symbol;

                WebClient webClient = new WebClient();

                webClient.DownloadDataCompleted += (sender, e) =>
                {
                    OHLC item = new OHLC();
                    if (e.Error != null) result(false, 0, handle);
                    else
                    {
                        var data = new System.Text.UTF8Encoding().GetString(e.Result);

                        if (!data.Contains("Prev Close:"))
                        {
                            result(false, 0, handle);
                            return;
                        }

                        int index = data.IndexOf("Prev Close:");
                        index = data.IndexOf('>', index);
                        index = data.IndexOf('>', index + 1);

                        int endIndex = data.IndexOf('<', index);

                        string priceString = data.Substring(index + 1, endIndex - index - 1);

                        decimal price;

                        if (!Decimal.TryParse(priceString, out price))
                        {
                            result(false, 0, handle);
                            return;
                        }
      
                        // we have the values!
                        result(true, price, handle);
                    }

                };

                webClient.DownloadDataAsync(new Uri(query));

                return true;
            }
            catch (Exception e)
            {
                Console.WriteLine("Failed TryGetHistorySpecificAsync for {0}", symbol);
                Console.WriteLine(e);
            }
            return false;
        } 


        /// <summary>
        /// Gets the last quote and creates a OHLC record for it
        /// </summary>
        /// <param name="symbol"></param>
        /// <param name="date"></param>
        /// <param name="item"></param>
        /// <returns></returns>
        public static bool TryGetQuote(ref string symbol, out DateTime date, out OHLC item)
        {
            string[] elements;
            date = DateTime.MinValue;
            item = new OHLC();

            // check we got the data straight off the bat
            if (!TryExtractQuote(symbol, out elements))
            {
                // failed to get data so perhaps symbol is wrong, let try and get the correct symbol
                bool found = TryExtractSymbolQuote(symbol, out symbol);
                if (!found || (found && !TryExtractQuote(symbol, out elements)))
                {
                    // failed to get data so perhaps symbol is wrong, let try and get the correct symbol
                    if (!TryExtractSymbolRedirect(symbol, out symbol))
                        return false;

                    // ok we got a new symbol let try get the data again
                    if (!TryExtractQuote(symbol, out elements))
                        return false;
                }                
            }

            // ok we got the data let's parse it
            
            decimal price;
            long volume;

            //System.Globalization.CultureInfo ukCulture = new System.Globalization.CultureInfo("en-US");
            //DateTime dateNew = DateTime.Parse(elements[QUOTE_DATE], ukCulture.DateTimeFormat);
            if (!DateTime.TryParseExact(elements[QUOTE_DATE], "MM/dd/yyyy", System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out date))
                return false;

            if (!Decimal.TryParse(elements[QUOTE_OPEN], out price))
                return false;
            item.Open = price;

            if (!Decimal.TryParse(elements[QUOTE_HIGH], out price))
                return false;
            item.High = price;

            if (!Decimal.TryParse(elements[QUOTE_LOW], out price))
                return false;
            item.Low = price;

            if (!Decimal.TryParse(elements[QUOTE_CLOSE], out price))
                return false;
            item.Close = price;

            if (!Int64.TryParse(elements[QUOTE_VOLUME], out volume))
                return false;
            item.Volume = volume;


            return true;
        }


        /// <summary>
        /// Gets the last quote and creates a OHLC record for it
        /// </summary>
        /// <param name="symbol"></param>
        /// <param name="date"></param>
        /// <param name="item"></param>
        /// <returns></returns>
        public static bool TryGetLast(ref string symbol, out decimal price)
        {
            string[] elements;

            price = 0;

            // check we got the data straight off the bat
            if (!TryExtractLast(symbol, out elements))
            {
                // failed to get data so perhaps symbol is wrong, let try and get the correct symbol
                bool found = TryExtractSymbolQuote(symbol, out symbol);
                if (!found || (found && !TryExtractLast(symbol, out elements)))
                {
                    // failed to get data so perhaps symbol is wrong, let try and get the correct symbol
                    if (!TryExtractSymbolRedirect(symbol, out symbol))
                        return false;

                    // ok we got a new symbol let try get the data again
                    if (!TryExtractLast(symbol, out elements))
                        return false;
                }
            }

            // ok we got the data let's parse it

            if (!Decimal.TryParse(elements[LAST_PRICE], out price))
                return false;

            return true;
        }



        private static bool TryExtractHistory(string symbol, out string[] data)
        {
            WebClient webClient = new WebClient();
            try
            {
                data = webClient.DownloadString(UrlHistory + symbol).Split('\n');

                if (data.Length > 2 || data[0].IndexOf("Date,Open,High,Low,Close,Volume,Adj Close") == 0)
                    return true;
            }
            catch
            {
                data = null;
            }
            
            return false;
        }


        private static bool TryExtractHistorySingle(string symbol, out string[] data)
        {
            WebClient webClient = new WebClient();
            try
            {
                using (StreamReader reader = new StreamReader(webClient.OpenRead(UrlHistory + symbol)))
                {
                    string line = reader.ReadLine();

                    if (line.IndexOf("Date,Open,High,Low,Close,Volume,Adj Close") == 0)
                    {
                        line = reader.ReadLine();

                        data = line.Split(',');

                        return true;
                    }

                    data = null;
                }
            }
            catch
            {
                data = null;
            }

            return false;
        }



        private static bool TryExtractHistorySpecific(string symbol, DateTime date, out string[] data)
        {
            WebClient webClient = new WebClient();
            try
            {
                // Yahoo! subtracks one months for some reason???
                date = date.AddMonths(-1);
                using (StreamReader reader = new StreamReader(webClient.OpenRead(String.Format(UrlHistorySpecific, symbol, date))))
                {
                    string line = reader.ReadLine();

                    if (line.IndexOf("Date,Open,High,Low,Close,Volume,Adj Close") == 0)
                    {
                        line = reader.ReadLine();

                        data = line.Split(',');

                        return true;
                    }

                    data = null;
                }
            }
            catch
            {
                data = null;
            }

            return false;
        }



        private static bool TryExtractQuote(string symbol, out string[] data)
        {
            WebClient webClient = new WebClient();
            try
            {
                string line = webClient.DownloadString(UrlQuote + symbol);

                data = line.Split('\n');

                if (data.Length == 2)
                {
                    data = data[0].Split(',');
                    if (data.Length == 8)
                        return true;
                }

                data = null;
                
            }
            catch
            {
                data = null;
            }

            return false;
        }


        private static bool TryExtractLast(string symbol, out string[] data)
        {
            WebClient webClient = new WebClient();
            try
            {
                string line = webClient.DownloadString(UrlLast + symbol);

                data = line.Split('\n');

                if (data.Length == 2)
                {
                    data = data[0].Split(',');
                    if (data.Length == 2)
                        return true;
                }

                data = null;

            }
            catch
            {
                data = null;
            }

            return false;
        } 


        /// <summary>
        /// If you use the wrong symbol on a quote check yahoo attempts to get you a correct one
        /// </summary>
        /// <param name="symbolIn"></param>
        /// <param name="symbolOut"></param>
        /// <returns></returns>
        private static bool TryExtractSymbolQuote(string symbolIn, out string symbolOut)
        {
            WebClient webClient = new WebClient();
            symbolOut = String.Empty;
            try
            {
                string data = webClient.DownloadString(UrlQuote + symbolIn);

                int pos = data.IndexOf(',');

                // MAGIK NUMB3RZ - I think that the symbol should be at least chars long... but seriously this is a pretty shit check!
                if (pos > 3)
                {
                    symbolOut = data.Substring(0, pos);
                    return true;
                }
            }
            catch
            {
                symbolOut = String.Empty;
            }


            return false;
        }

        /// <summary>
        /// If you use the wrong symbol on a quote check yahoo attempts to get you a correct one
        /// </summary>
        /// <param name="symbolIn"></param>
        /// <param name="symbolOut"></param>
        /// <returns></returns>
        private static bool TryExtractSymbolRedirect(string symbolIn, out string symbolOut)
        {
            WebClient webClient = new WebClient();
            symbolOut = String.Empty;
            try
            {
                // try see if there is a a symbol redirect
                string data = webClient.DownloadString(UrlQueryRedirect + symbolIn);

                int pos = data.IndexOf("It has changed to ");

                if (pos > 0)
                {
                    pos = data.IndexOf("<a href=\"/q?s=", pos) + "<a href=\"/q?s=".Length;
                    symbolOut = data.Substring(pos, data.IndexOf("\">", pos) - pos);
                    return true;
                }
            }
            catch
            {
                symbolOut = String.Empty;
            }


            return false;
        }
    }
}
